﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace cpktools
{
    public struct TypeTable
    {
        public static Type[] tbl = { typeof(Byte),typeof(SByte),
                                   typeof(UInt16),typeof(Int16),
                                   typeof(UInt32),typeof(Int32),
                                   typeof(UInt64),typeof(Int64),
                                   typeof(Single),typeof(Double),
                                   typeof(String),typeof(Int32)};
    }
    public struct UTFColumnInfo
    {
        public byte type;
        public string column_name;
        public int constant_offset;
    };
    public struct TableInfo
    {
        public uint table_size; //2c0
        public uint rows_offset; //c7
        public uint string_table_offset;//14D
        public uint data_offset;//2C0
        public ushort columns;
        public ushort row_width;
        public uint rows;

    }
    class UTFTable
    {
        TableInfo tbi;
        UTFColumnInfo[] schema;
        DataTable dt;
        public DataTable GetTable { get { return dt; } }
        DataTable const_dt;
        byte[] hdata;
        char[] str_area;
        int offset;
        public int Offset { get { return offset; } }
        uint table_name_string_offset;
        private static byte[] HeadExpect={0x40,0x55,0x54,0x46};
        public UTFTable(byte[] hdata,int offset)
        {
            this.hdata = hdata;
            this.offset = offset;
            AnaylzeTable();
            
        }
        void AnaylzeTable()
        {
            if (!isHeadOK(hdata))
                throw new Exception("文件头找不到@UTF");

            //UTF表信息
            tbi.table_size =ToUInt32BE(hdata,0x4);
            tbi.rows_offset = ToUInt32BE(hdata, 0x8);
            tbi.string_table_offset = ToUInt32BE(hdata, 0xC);
            tbi.data_offset = ToUInt32BE(hdata, 0x10);
            table_name_string_offset = ToUInt32BE(hdata, 0x14);
            tbi.columns = ToUInt16BE(hdata, 0x18);
            tbi.row_width = ToUInt16BE(hdata, 0x1A);
            tbi.rows = ToUInt32BE(hdata, 0x1C);

            uint string_length=tbi.data_offset-tbi.string_table_offset;
            str_area = new char[string_length];
            str_area = ASCIIEncoding.ASCII.GetChars(hdata, (int)tbi.string_table_offset + 8, (int)string_length);
            

            //UTF表头
            dt=new DataTable(GetStringFromStrArea(table_name_string_offset));
            const_dt = new DataTable(dt.TableName);
            schema = new UTFColumnInfo[tbi.columns];
            uint nowaddr = 0x20;
            for (int i = 0; i < tbi.columns; i++)
            {
                
                byte tpbyte=hdata[nowaddr++];
                schema[i].type = tpbyte;
                uint str_offset = ToUInt32BE(hdata,nowaddr);
                nowaddr += 4;
                string str = GetStringFromStrArea(str_offset);
                schema[i].column_name = str;
                int t1,t2;
                t1=tpbyte&0xf0;
                t2=tpbyte&0x0f;
                DataColumn dc = new DataColumn(str, TypeTable.tbl[t2]);
                switch (t1)
                {
                    case 0x10:
                        dc.AllowDBNull = true;
                        dc.DefaultValue = null;
                        break;
                    case 0x30:
                        dc.AllowDBNull = false;
                        const_dt.Columns.Add(str,TypeTable.tbl[t2]);
                        DataRow dr;
                        if (const_dt.Rows.Count == 0)
                            dr = const_dt.NewRow();
                        else
                            dr = const_dt.Rows[0];

                        dr[str] = GetData(ref nowaddr, (byte)t2);
                        dc.DefaultValue = dr[str];
                        if (const_dt.Rows.Count == 0)
                            const_dt.Rows.Add(dr);
                        break;
                    case 0x50:
                        dc.AllowDBNull = false;
                        break;
                    default:
                        throw new Exception(string.Format("错误类型标识：0x{0:X2}" ,tpbyte));
                }
                if (t2<0xb)
                    dt.Columns.Add(dc);
                else
                    throw new Exception(string.Format("错误类型标识：0x{0:X2}", tpbyte));
            }
            //数据
            for (uint i = 0; i < tbi.rows; i++)
            {
                DataRow dr = dt.NewRow();
                uint row_offset = tbi.rows_offset + 8 + i * tbi.row_width;
                //dr.BeginEdit();
                for (int j = 0; j < tbi.columns; j++)
                {
                    if (dt.Columns[j].AllowDBNull ||const_dt.Columns.Contains(dt.Columns[j].ColumnName))
                        continue;
                    else
                    {
                        object obj=dr[j];
                        byte type=(byte)(schema[j].type & 0xf);
                        dr[j]=GetData(ref row_offset, type);
                    }//else
                }//for row
                dt.Rows.Add(dr);
            }//for column
        }

        private object GetData(ref uint row_offset, byte type)
        {
            object obj;
            switch (type)
            {
                case 0x6:
                    obj = ToUInt64BE(hdata, row_offset);
                    row_offset += sizeof(Int64);
                    break;
                case 0x5:
                    obj = Convert.ToInt32(ToUInt32BE(hdata, row_offset));
                    row_offset += sizeof(Int32);
                    break;
                case 0x4:
                    obj = ToUInt32BE(hdata, row_offset);
                    row_offset += sizeof(Int32);
                    break;
                case 0x2:
                    obj = ToUInt16BE(hdata, row_offset);
                    row_offset += sizeof(Int16);
                    break;
                case 0xA:
                    uint str_offset = ToUInt32BE(hdata, row_offset);
                    obj = this.GetStringFromStrArea(str_offset);
                    row_offset += sizeof(Int32);
                    break;
                default:
                    throw new NotImplementedException(string.Format("未实现类型标识：0x{0:X2}", type));
            }
            return obj;
        }
        private void WriteData(object obj, byte[] bytes, ref uint row_offset, ref int str_offset, byte type)
        {
            switch (type)
            {
                case 0x6:
                    WriteBE(Convert.ToUInt64(obj),bytes,row_offset);
                    row_offset += sizeof(Int64);
                    break;
                case 0x5:
                    WriteBE(Convert.ToUInt32(obj),bytes,row_offset);
                    row_offset += sizeof(UInt32);
                    break;
                case 0x4:
                    WriteBE(Convert.ToUInt32(obj),bytes,row_offset);
                    row_offset += sizeof(UInt32);
                    break;
                case 0x2:
                    WriteBE(Convert.ToUInt16(obj), bytes, row_offset);
                    row_offset += sizeof(UInt16);
                    break;
                default:
                    throw new NotImplementedException(string.Format("未实现类型标识：0x{0:X2}", type));
            }
        }
        private string GetStringFromStrArea(uint str_offset)
        {
            StringBuilder sb = new StringBuilder();
            while (str_area[str_offset] > 0)
            {
                sb.Append(str_area[str_offset]);
                str_offset++;
            }
            string str = sb.ToString();
            return str;
        }

        bool isHeadOK(byte[] head)
        {
            for (int i = 0; i < HeadExpect.Length; i++)
            {
                if (head[i] != HeadExpect[i]) return false;
            }
            return true;
        }
        public void Output()
        {
            /*foreach (UTFColumnInfo ti in schema)
            {
                Console.WriteLine(string.Format("{0:X2} {1}",ti.type,ti.column_name));
            }*/
            foreach (DataColumn dc in dt.Columns)
            {
                Console.Write(dc.ColumnName+"\t");
            }
            Console.WriteLine("\t");
            foreach (DataRow dr in dt.Rows)
            {
                StringBuilder sb=new StringBuilder();
                foreach (DataColumn dc in dt.Columns)
                {
                    if (dc.AllowDBNull)
                        sb.Append("UNDEFINED\t");
                    else
                    {
                        if (dr[dc.ColumnName] is string)
                            sb.Append(dr[dc.ColumnName]);
                        else if (dc.ColumnName.Contains("Time"))
                        {
                            long timestamp = Convert.ToInt64(dr[dc.ColumnName]);
                            DateTime dtm = DateTime.FromBinary(timestamp);
                            //TimeSpan span = (timestamp - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());

                            sb.Append(dtm.ToString());
                        }
                        else
                            sb.AppendFormat("0x{0:X}", dr[dc.ColumnName]);
                    }
                    sb.Append('\t');
                }
                Console.WriteLine(sb.ToString());
            }
        }
        public byte[] PackTable()
        {
            uint aliensize = (tbi.table_size + 8 - 1) / 0x800 * 0x800 + 0x800;
            byte[] ret = new byte[aliensize];

            //UTF表信息
            uint nowaddr=0;
            int tmp=0;
            WriteStringToStrArea(ret, ref tmp , "@UTF");
            WriteBE(tbi.table_size, ret, 0x4);//暂定
            WriteBE(tbi.rows_offset, ret, 0x8);
            WriteBE(tbi.string_table_offset, ret, 0xC);
            WriteBE(tbi.data_offset, ret, 0x10);
            WriteBE(table_name_string_offset, ret, 0x14);
            WriteBE(tbi.columns, ret, 0x18);
            WriteBE(tbi.row_width, ret, 0x1A);
            WriteBE(tbi.rows, ret, 0x1C);
            byte[] str_area=new byte[0x1000];
            int str_offset = 0;
            WriteStringToStrArea(str_area, ref str_offset, "<NULL>");
            WriteStringToStrArea(str_area, ref str_offset, dt.TableName);

            nowaddr=0x20;
            for (int i = 0; i < schema.Length; i++)
            {
                ret[nowaddr++] = schema[i].type;//类型字节
                WriteBE((uint)str_offset, ret, nowaddr);//列名字符串
                nowaddr += 4;
                WriteStringToStrArea(str_area, ref str_offset, schema[i].column_name);
                if (const_dt.Columns.Contains(schema[i].column_name))
                {
                    if ((schema[i].type & 0xf) == 0xa)
                    {
                        string str = (string)const_dt.Rows[0][schema[i].column_name];
                        if (str == "<NULL>")
                            WriteBE(0, ret, nowaddr);
                        else
                        {
                            WriteBE((uint)str_offset, ret, nowaddr);
                            WriteStringToStrArea(str_area, ref str_offset, str);
                        }
                        nowaddr += sizeof(Int32);
                    }
                    else
                        WriteData(const_dt.Rows[0][schema[i].column_name], str_area, ref nowaddr, ref str_offset, (byte)(schema[i].type & 0xf));
                }

            }
            for (uint i = 0; i < tbi.rows; i++)
            {
                DataRow dr = dt.Rows[(int)i];
                uint row_offset = tbi.rows_offset + 8 + i * tbi.row_width;
                //dr.BeginEdit();
                for (int j = 0; j < tbi.columns; j++)
                {
                    if (dt.Columns[j].AllowDBNull || const_dt.Columns.Contains(dt.Columns[j].ColumnName))
                        continue;
                    else
                    {
                        object obj = dr[j];
                        byte type = (byte)(schema[j].type & 0xf);
                        if ((schema[j].type & 0xf) == 0xa)
                        {
                            WriteBE((uint)str_offset,ret,row_offset);
                            WriteStringToStrArea(str_area,ref str_offset,(string)obj);
                            row_offset += sizeof(Int32);
                        }else
                            WriteData(dr[j], ret, ref row_offset, ref str_offset, (byte)(schema[j].type & 0xf));
                    }//else
                }//for row
            }//for column
            Array.Copy(str_area, 0, ret, tbi.string_table_offset + 8, str_offset);
            return ret;

        }
        public object[] GetColumn(string ColumnName)
        {
            if (!dt.Columns.Contains(ColumnName))
                return null;
            object[] ret=new object[dt.Rows.Count];
            int i = 0;
            foreach (DataRow dr in dt.Rows)
            {
                ret[i++] = dr[ColumnName];
            }
            return ret;
        }
        //大小端转化
        UInt64 ToUInt64BE(byte[] bytes, uint index)
        {
            byte[] bytesBE = new byte[sizeof(long)];
            Array.Copy(bytes, index, bytesBE, 0, sizeof(long));
            Array.Reverse(bytesBE);
            return BitConverter.ToUInt64(bytesBE, 0);
        }
        UInt32 ToUInt32BE(byte[] bytes,uint index)
        {
            byte[] bytesBE=new byte[sizeof(int)];
            Array.Copy(bytes,index,bytesBE,0,sizeof(int));
            Array.Reverse(bytesBE);
            return BitConverter.ToUInt32(bytesBE, 0);
        }
        UInt16 ToUInt16BE(byte[] bytes, uint index)
        {
            byte[] bytesBE = new byte[sizeof(short)];
            Array.Copy(bytes, index, bytesBE, 0, sizeof(short));
            Array.Reverse(bytesBE);
            return BitConverter.ToUInt16(bytesBE, 0);
        }
        //大小端转化写
        void WriteBE(UInt64 value, byte[] bytes, uint index)
        {
            byte[] tmp=BitConverter.GetBytes(value);
            Array.Reverse(tmp);
            Array.Copy(tmp, 0, bytes, index, tmp.Length);
        }
        void WriteBE(UInt32 value, byte[] bytes, uint index)
        {
            byte[] tmp = BitConverter.GetBytes(value);
            Array.Reverse(tmp);
            Array.Copy(tmp, 0, bytes, index, tmp.Length);
        }
        void WriteBE(UInt16 value, byte[] bytes, uint index)
        {
            byte[] tmp = BitConverter.GetBytes(value);
            Array.Reverse(tmp);
            Array.Copy(tmp, 0, bytes, index, tmp.Length);
        }
        private void WriteStringToStrArea(byte[] bytes, ref int str_offset,string str)
        {
            ASCIIEncoding.ASCII.GetBytes(str, 0, str.Length, bytes, str_offset);
            str_offset += str.Length + 1;
        }
    }
}
